﻿package com.janisRadins.asDraw {
	/**
	 * This class base calculation are developed by Alex Uhlmann. For which I'm realy thankful to that man. <br>
	 * I just did a proper port to AS3 and added some decoration for usability
	 * @author Jānis Radiņš
	 * 
	 * 2009-04-21 lmg@kishalmi.net
	 *  FIX: intersect2lines - only return null if both dx1 AND dx2 are zero
	 *  FIX: calculateQuad - split the curve if intersection is impossible
	 */
	
	import flash.geom.Point;
	public final class CubicCurve {
		private var _p1x:Number = 0;
		private var _p1y:Number = 0;
		private var _c1x:Number = 0;
		private var _c1y:Number = 0;
		private var _c2x:Number = 0;
		private var _c2y:Number = 0;
		private var _p2x:Number = 0;
		private var _p2y:Number = 0;

		private var _tolerance:uint = 1;
		private var _quadratics:Array = null;

		/**
		 * Constructor
		 * @param       p1x     First anchor point x value
		 * @param       p1y     First anchor point y value
		 * @param       c1x     First controll point x value
		 * @param       c1y     First controll point y value
		 * @param       c2x     Second controll point x value
		 * @param       c2y     Second controll point y value
		 * @param       p2x     Second anchor point y value
		 * @param       p2y     Second anchor point y value
		 */
		public function CubicCurve(p1x:Number = NaN, p1y:Number = NaN, c1x:Number = NaN, c1y:Number = NaN, c2x:Number = NaN, c2y:Number = NaN, p2x:Number = NaN, p2y:Number = NaN) {
			if (!isNaN(p1x))	_p1x = p1x;
			if (!isNaN(p1y))	_p1y = p1y;
			if (!isNaN(c1x))	_c1x = c1x;
			if (!isNaN(c1y))	_c1y = c1y;
			if (!isNaN(c2x))	_c2x = c2x;
			if (!isNaN(c2y))	_c2y = c2y;
			if (!isNaN(p2x))	_p2x = p2x;
			if (!isNaN(p2y))	_p2y = p2y;
		}
		/**
		 * First anchor point x value
		 */
		public function get p1x():Number {
			return _p1x;
		}
		public function set p1x(value:Number):void {
			if (_p1x == value)
				return ;
			_p1x = value;
			_quadratics = null;
		}
		/**
		 * First anchor point y value
		 */
		public function get p1y():Number {
			return _p1y;
		}
		public function set p1y(value:Number):void {
			if (_p1y == value)
				return ;
			_p1y = value;
			_quadratics = null;
		}
		/**
		 * First controll point x value
		 */
		public function get c1x():Number {
			return _c1x;
		}
		public function set c1x(value:Number):void {
			if (_c1x == value)
				return ;
			_c1x = value;
			_quadratics = null;
		}
		/**
		 * First controll point y value
		 */
		public function get c1y():Number {
			return _c1y;
		}
		public function set c1y(value:Number):void {
			if (_c1y == value)
				return ;
			_c1y = value;
			_quadratics = null;
		}
		/**
		 * Second controll point x value
		 */
		public function get c2x():Number {
			return _c2x;
		}
		public function set c2x(value:Number):void {
			if (_c1x == value)
				return ;
			_c2x = value;
			_quadratics = null;
		}
		/**
		 * Second controll point y value
		 */
		public function get c2y():Number {
			return _c2y;
		}
		public function set c2y(value:Number):void {
			if (_c2y == value)
				return ;
			_c2y = value;
			_quadratics = null;
		}
		/**
		 * Second anchor point y value
		 */
		public function get p2x():Number {
			return _p2x;
		}
		public function set p2x(value:Number):void {
			if (_p1x == value)
				return ;
			_p2x = value;
			_quadratics = null;
		}
		/**
		 * Second anchor point y value
		 */
		public function get p2y():Number {
			return _p2y;
		}
		public function set p2y(value:Number):void {
			if (_p2x == value)
				return ;
			_p2y = value;
			_quadratics = null;
		}
		/**
		 * Tolerance of Cubic to Quadratic curve calulation
		 */
		public function get tolerance():uint {
			return _tolerance;
		}
		public function set tolerance(value:uint):void {
			_tolerance = value;
			_quadratics = null;
		}
		/**
		 * Array of quadratic curve data which should represent atleast pretty close analogue to Cubic curve data this class holds
		 */
		public function get quadratics():Array {
			if (_quadratics == null) {
				_quadratics = new Array();
				calculateQuad(new CDH(new Point(_p1x, _p1y), new Point(_c1x, _c1y), new Point(_c2x, _c2y), new Point(_p2x, _p2y)));
			}
			return _quadratics;
		}
		/**
		 * Methot that returns point on Cubic bezzier curve
		 * @param       t
		 * Time value of point to be displayed in range 0-1
		 * @return bloody point
		 */
		public function getPoint(t:Number):Point {
			return new Point(Math.pow(1 - t, 3) * _p1x + 3 * Math.pow(1 - t, 2) * t * _c1x + 3 * (1 - t) * Math.pow(t, 2) * _c2x + Math.pow(t, 3) * _p2x, Math.pow(1 - t, 3) * _p1y + 3 * Math.pow(1 - t, 2) * t * _c1y + 3 * (1 - t) * Math.pow(t, 2) * _c2y + Math.pow(t, 3) * _p2y);
		}
		/**
		 * @return Clone of this Cubic curve instance
		 */
		public function clone():CubicCurve {
			return new CubicCurve(_p1x, _p1y, _c1x, _c1y, _c2x, _c2y, _p2x, _p2y);
		}
		private function calculateQuad(data:CDH):void {
			var halves:Array;
			// find intersection between bezier arms
			var s:Point = intersect2Lines(data);
			if( s == null ) {
				// no intersection possible (parallel handles) -> split curve in half
				halves = bezierSplit(data);
				calculateQuad(halves[0]);
				calculateQuad(halves[1]);
				return;
			}
			// find distance between the midpoints
			var dx:Number = (data.p1.x + data.p2.x + s.x * 4 - (data.c1.x + data.c2.x) * 3) * 0.125;
			var dy:Number = (data.p1.y + data.p2.y + s.y * 4 - (data.c1.y + data.c2.y) * 3) * 0.125;
			if (dx * dx + dy * dy > _tolerance) {
				// quadratic isn't close enough -> split curve in half
				halves = bezierSplit(data);
				calculateQuad(halves[0]);
				calculateQuad(halves[1]);
				return;
			}
			// save points (eventually end recursion)
			_quadratics.push([data.p1.x, data.p1.y, s.x, s.y, data.p2.x, data.p2.y]);
		}
		private function intersect2Lines(data:CDH):Point {
			var dx1:Number = data.c1.x - data.p1.x;
			var dx2:Number = data.c2.x - data.p2.x;
			if (!dx1 && !dx2) {
				return null;
			}
			var m1:Number = (data.c1.y - data.p1.y) / dx1;
			var m2:Number = (data.c2.y - data.p2.y) / dx2;
			if (!dx1) {
				return new Point(data.p1.x, m2 * (data.p1.x - data.p2.x) + data.p2.y);
			}
			if (!dx2) {
				return new Point(data.p2.x, m1 * (data.p2.x - data.p1.x) + data.p1.y);
			}
			var x:Number = (-m2 * data.p2.x + data.p2.y + m1 * data.p1.x - data.p1.y) / (m1 - m2);
			var y:Number = m1 * (x - data.p1.x) + data.p1.y;
			return new Point(x, y);
		}
		private function bezierSplit(data:CDH):Array {
			var p1:Point = data.p1.clone();
			var p2:Point = data.p2.clone();
			var p01:Point = midPoint(p1, data.c1);
			var p12:Point = midPoint(data.c1, data.c2);
			var p23:Point = midPoint(data.c2, data.p2);
			var p02:Point = midPoint(p01, p12);
			var p13:Point = midPoint(p12, p23);
			var p03:Point = midPoint(p02, p13);
			return [new CDH(p1, p01, p02, p03), new CDH(p03, p13, p23, p2)];
		}
		private function midPoint(p1:Point, p2:Point):Point {
			return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
		}
	}
}


import flash.geom.Point;

internal class CDH {
	public var p1:Point = null;
	public var c1:Point = null;
	public var c2:Point = null;
	public var p2:Point = null;

	public function CDH(p1:Point = null, c1:Point = null, c2:Point = null, p2:Point = null) {
		this.p1 = p1 || new Point();
		this.c1 = c1 || new Point();
		this.c2 = c2 || new Point();
		this.p2 = p2 || new Point();
	}

	public function toString():String {
		return "CDH(p1:" + p1 + ", c1:" + c1 + ", c2:" + c2 + ", p2:" + p2 + ")";
	}
}
